﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ScrumTable.BL.DM.DataManagement.Logging;
using ScrumTable.BL.DM.DataManagement.Tracking.EditCommands;
using ScrumTable.BL.DM.Properties;
using ScrumTable.Common;
using ScrumTable.Common.Collections;
using ScrumTable.Common.EventHandling;
using ScrumTable.Common.Threading;
using ScrumTable.DL.Data.Common;

#endregion

namespace ScrumTable.BL.DM.DataManagement
{
    /// <summary>
    /// This class specifies the concrete implementation for all domain data
    /// collections.
    /// </summary>
    public class DomainCollection<T> : DisposeBase, ICollection<T>, IDomainCollection
        where T : DomainBase, new()
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const string EvtKeyChanged = "OnKeyChanged";

        private readonly DelegateHandlerCollection<DomainCollectionChangedEventHandler> _events = new DelegateHandlerCollection<DomainCollectionChangedEventHandler>();
        private readonly bool _isReadOnly = false;
        private Dictionary<string, T> _dataStorage = null;

        private IDomainContext _context;
        private DomainBase _parent;
        private UpdateState _updateState = UpdateState.None;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the element <typeparamref name="T"/> with the specified id.
        /// </summary>
        /// <value>Returns the retrieved element.</value>
        public T this[string id]
        {
            get
            {
                if (!DataStorage.ContainsKey(id))
                {
                    IDataObject data = Data.GetById(ParentIds, id);

                    if (data == null)
                        throw new ArgumentException(Resources.ExcDmnColElementIdNotFound);

                    DataStorage[id] = DomainBase.CreateInstance<T>(_context, data, _parent);
                }
                return DataStorage[id];
            }
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="DomainCollection{T}"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The number of elements contained in the <see cref="DomainCollection{T}"/>.
        /// </returns>
        public int Count
        {
            get { return DataStorage.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="DomainCollection{T}"/> is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="DomainCollection{T}"/> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return _isReadOnly; }
        }

        /// <summary>
        /// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is synchronized (thread safe).
        /// </summary>
        /// <value></value>
        /// <returns>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized (thread safe); otherwise, false.
        /// </returns>
        public bool IsSynchronized
        {
            get { return false; }
        }

        /// <summary>
        /// Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// An object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"/>.
        /// </returns>
        public object SyncRoot
        {
            get { return this; }
        }


        /// <summary>
        /// Gets the public domain context.
        /// </summary>
        public IDomainContext Context
        {
            get { return _context; }
        }

        /// <summary>
        /// Gets the type which corresponds to the domain object types
        /// of the current collection.
        /// </summary>
        public Type ItemType
        {
            get { return typeof(T); }
        }

        /// <summary>
        /// Gets the internal domain context.
        /// </summary>
        internal DomainContext ContextInternal { get { return (DomainContext)_context; } }

        /// <summary>
        /// Gets or sets the underlying object which provides the data for the current collection.
        /// </summary>
        protected IDataAccessObject Data { get; private set; }

        private Dictionary<string, T> DataStorage
        {
            get
            {
                EnsureNotDisposed();

                if (_dataStorage == null)
                    LoadData(false);

                return _dataStorage;
            }
        }

        private ValueChain<string> ParentIds
        {
            get { return (_parent != null) ? _parent.IdChain : new ValueChain<string>(); }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new DomainCollection instance. This constructor
        /// is internal because its needed by the current assembly only.
        /// </summary>
        /// <param name="isReadOnly">Specifies if the collection should be read only.</param>
        /// <param name="context">Domain context which provides information about the object hierarchy.</param>
        /// <param name="parent">Parent which provides information about the object hierarchy.</param>
        /// <param name="data">Contains the underlying data access component.</param>
        internal protected DomainCollection(
            bool isReadOnly,
            IDomainContext context,
            DomainBase parent,
            IDataAccessObject data)
        {
            PreCondition.AssertNotNull(data, "data");
            
            Data = data;
            Data.DataAccessObjectChanged += OnDataAccessObjectChanged;
            _isReadOnly = isReadOnly;
            _context = context;
            _parent = parent;
        }


        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the element <typeparamref name="T"/> with the specified id.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        /// <value>Returns the retrieved element.</value>
        DomainBase IDomainCollection.TryGetById(string id)
        {
            return TryGetById(id);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0} [{1}]", base.ToString(), ParentIds.Chain);
        }

        /// <summary>
        /// Supports a simple type safe iteration over the current collection.
        /// </summary>
        /// <returns>Returns the iterator in order to loop over this collection.</returns>
        public IEnumerator<T> GetEnumerator()
        {
            EnsureNotDisposed();

            return (from domainBase in DataStorage.Values
                    orderby domainBase.DataObject.OrderIndex
                    select domainBase).GetEnumerator();
        }

        /// <summary>
        /// Supports a simple iteration over the current collection.
        /// </summary>
        /// <returns>Returns the iterator in order to loop over this collection.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<T>) this).GetEnumerator();
        }

        /// <summary>
        /// Refreshes the domain collection, disposes all children and
        /// reinitializes them.
        /// </summary>
        /// <param name="loadDeep">True to reload all data deep (which means with all child data).</param>
        public void Refresh(bool loadDeep = false)
        {
            _dataStorage = null;
            LoadData(loadDeep);
        }

        /// <summary>
        /// Gets the element <typeparamref name="T"/> with the specified id.
        /// </summary>
        /// <value>Returns the retrieved element.</value>
        public T TryGetById(string id)
        {
            if (!DataStorage.ContainsKey(id))
            {
                IDataObject data = Data.GetById(ParentIds, id);

                if (data == null)
                    return null;

                DataStorage[id] = DomainBase.CreateInstance<T>(_context, data, _parent);
            }
            return DataStorage[id];
        }

        /// <summary>
        /// Gets the element <typeparamref name="T"/> with the specified name.
        /// </summary>
        /// <value>Returns the retrieved element.</value>
        public T TryGetByName(string name)
        {
            foreach (T element in this)
            {
                if (element.Name == name)
                    return element;
            }
            return null;
        }

        /// <summary>
        /// Creates a new element with a new server side id. Thus, after calling
        /// this method, the element is physically created on server side.
        /// After creating the element, it's assigned to the current collection.
        /// 
        /// To assgin an existing element, you should use the <see cref="ICollection{T}.Add"/>
        /// method.
        /// </summary>
        /// <returns>Returns the created element.</returns>
        public T AddNew()
        {
            ActionLogger.InfoFormat(Resources.LogActDmnCollAddNew, ItemType.Name, ParentIds);

            EnsureNotDisposed();

            if (_isReadOnly)
                throw new NotSupportedException(Resources.ExcDmnColReadOnly);
            
            // first we have to initialize the data storage (this calls lazy load procedures)
            // otherwise the lazy laod procedure would load the just inserted data object
            // twice
            Dictionary<string, T> dataStorage = DataStorage;

            // insert element on data layer level
            T newElement = DomainBase.CreateInstance<T>(_context, Data.Insert(ParentIds), _parent);
            
            // finnaly, add created element to internal cache. the storage must be loaded
            // before inserting an element on data layer level.
            AddNew(dataStorage, newElement);
            return newElement;
        }

        /// <summary>
        /// Adds an item to the current <see cref="DomainCollection{T}"/>. This
        /// procedure is for Moving elements from one collection to the other.
        /// If you'd like to add a new element, use <see cref="DomainCollection{T}.AddNew()"/> instead.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="DomainCollection{T}"/>.</param>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="DomainCollection{T}"/> is read-only.
        /// </exception>
        public void Add(T item)
        {
            ValueChain<Pair<string, Type>> oldIdChain = item.TypedIdChain;

            try { AddInternal(item); }
            finally { _context.UndoManager.AddEdit(new DomainCollectionUndoMoveCommand<T>(item.Name,oldIdChain, item.TypedIdChain)); }
        }

        /// <summary>
        /// Removes all items from the <see cref="DomainCollection{T}"/>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="DomainCollection{T}"/> is read-only.
        /// </exception>
        public void Clear()
        {
            EnsureNotDisposed();

            if (_isReadOnly)
                throw new NotSupportedException(Resources.ExcDmnColReadOnly);

            DataStorage.Clear();
            Data.Delete(ParentIds);

            OnChanged(new DomainCollectionChangedEventArgs(DomainCollectionChangedAction.Reset, this));
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="DomainCollection{T}"/>.
        /// </summary>
        /// <param name="item">The object to remove from the <see cref="DomainCollection{T}"/>.</param>
        /// <returns>
        /// true if <paramref name="item"/> was successfully removed from the
        /// <see cref="DomainCollection{T}"/>; otherwise, false. This method also returns
        /// false if <paramref name="item"/> is not found in the original <see cref="DomainCollection{T}"/>.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">
        /// The <see cref="DomainCollection{T}"/> is read-only.
        /// </exception>
        public bool Remove(T item)
        {
            EnsureNotDisposed();

            if (_isReadOnly)
                throw new NotSupportedException(Resources.ExcDmnColReadOnly);

            return Remove(item, true);
        }

        /// <summary>
        /// Determines whether the <see cref="DomainCollection{T}"/> contains a specific value.
        /// </summary>
        /// <param name="item">The object to locate in the <see cref="DomainCollection{T}"/>.</param>
        /// <returns>
        /// true if <paramref name="item"/> is found in the <see cref="DomainCollection{T}"/>; otherwise, false.
        /// </returns>
        public bool Contains(T item)
        {
            PreCondition.AssertNotNull(item, "item");

            EnsureNotDisposed();
            return ContainsId(item.Id);
        }

        /// <summary>
        /// Checks wheter the element <typeparamref name="T"/> is contained
        /// in the current collection.
        /// </summary>
        /// <value>Returns true, if an element with the given id exists in the
        /// current collection.</value>
        public bool ContainsId(string id)
        {
            PreCondition.AssertNotNull(id, "id");

            EnsureNotDisposed();
            return DataStorage.ContainsKey(id);
        }

        /// <summary>
        /// Copies the elements of the <see cref="DomainCollection{T}"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the
        /// destination of the elements copied from <see cref="DomainCollection{T}"/>. The <see cref="T:System.Array"/>
        /// must have zero-based indexing.</param>
        /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="array"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// 	<paramref name="arrayIndex"/> is less than 0.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// 	<paramref name="array"/> is multidimensional.
        /// -or-
        /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>.
        /// -or-
        /// The number of elements in the source <see cref="DomainCollection{T}"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.
        /// </exception>
        public void CopyTo(T[] array, int arrayIndex)
        {
            EnsureNotDisposed();
            CopyTo((Array)array, arrayIndex);
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param>
        /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="array"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// 	<paramref name="arrayIndex"/> is less than zero.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// 	<paramref name="array"/> is multidimensional.
        /// -or-
        /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>.
        /// -or-
        /// The number of elements in the source <see cref="T:System.Collections.ICollection"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// The type of the source <see cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.
        /// </exception>
        public void CopyTo(Array array, int arrayIndex)
        {
            PreCondition.AssertNotNull(array, "array");
            PreCondition.AssertInRange(-1, array.Length, arrayIndex, "arrayIndex");

            EnsureNotDisposed();

            foreach (T element in this)
            {
                array.SetValue(element, arrayIndex);

                if (++arrayIndex >= array.Length)
                    break;
            }
        }

        /// <summary>
        /// Releases the internal data and refreshes the data storage.
        /// </summary>
        /// <param name="deep">True to (re)load the children of the curent domain collection.</param>
        /// <remarks>
        /// Undoing this method is neither allowed nor supported.
        /// </remarks>
        public void LoadData(bool deep)
        {
            ActionLogger.InfoFormat(Resources.LogActDmnCollLoadDataSync, ItemType.Name, ParentIds, deep);

            EnsureNotDisposed();
            PrepareDataLoad();

            try
            {
                InitDataStorageElements(Data.GetAll(ParentIds));

                if (deep)
                {
                    foreach (KeyValuePair<string, T> dataObjectPair in _dataStorage)
                    {
                        dataObjectPair.Value.LoadChildData();
                    }
                }
            }
            finally
            {
                _updateState = UpdateState.None;
            }
        }

        /// <summary>
        /// Releases the internal data and refreshes the data storage.
        /// </summary>
        /// <param name="deep">True to (re)load the children of the curent domain collection.</param>
        /// <param name="callback">Specifies the asynchronous callback delegate. The callback value is of type DataResult.</param>
        /// <remarks>
        /// Undoing this method is neither allowed nor supported.
        /// </remarks>
        public void LoadDataAsync(bool deep, AsyncCallback callback)
        {
            PreCondition.AssertNotNull(callback, "callback");
            ActionLogger.InfoFormat(Resources.LogActDmnCollLoadDataASync, ItemType.Name, ParentIds, deep);

            EnsureNotDisposed();
            PrepareDataLoad();
            _updateState |= UpdateState.CallbackPending;

            AsyncCallbackHandler asyncHandler = new AsyncCallbackHandler(
                this,
                result =>
                {
                    _updateState = UpdateState.None;
                    callback(result);
                });

            if (deep)
                asyncHandler.InitDeep();
            else
                asyncHandler.InitFlat();
        }

        /// <summary>
        /// Internal add functionality which is used to support a change
        /// without undo/redo registration.
        /// </summary>
        internal void AddInternal(T item)
        {
            ActionLogger.InfoFormat(Resources.LogActDmnCollMove, ItemType.Name, ParentIds, item.ParentIdChain);

            EnsureNotDisposed();

            if (_isReadOnly)
                throw new NotSupportedException(Resources.ExcDmnColReadOnly);

            if (item.Parent == _parent)
                throw new InvalidOperationException(Resources.ExcDmnColItemExists);

            AddInternal(item, true);
        }

        /// <summary>
        /// Releases managed (c# or .net) resources.
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            Data.DataAccessObjectChanged -= OnDataAccessObjectChanged;
            Data = null;

            DisposeChildren();

            _dataStorage = null;
            _context = null;
            _parent = null;
        }

        private void DisposeChildren()
        {
            if (_dataStorage == null)
                return;

            _dataStorage.ForEach(childElement => childElement.Value.Dispose());
        }

        private void PrepareDataLoad()
        {
            if (_updateState != UpdateState.None)
                throw new InvalidOperationException(Resources.ExcDmnColAsyncCallbackPending);

            if (_dataStorage != null)
                _updateState |= UpdateState.RefreshPending;

            DisposeChildren();
            _dataStorage = null;
        }

        private void InitDataStorageElements(ICollection<IDataObject> dataObjects)
        {
            _dataStorage = new Dictionary<string, T>();

            foreach (IDataObject dataObject in dataObjects)
            {
                _dataStorage[dataObject.Id] = DomainBase.CreateInstance<T>(_context, dataObject, _parent);
            }

            if ((_updateState & UpdateState.RefreshPending) != 0)
            {
                OnChanged(new DomainCollectionChangedEventArgs(DomainCollectionChangedAction.Reset, this));
            }
        }

        private void AddInternal(T item, bool doUpdate)
        {
            if (doUpdate)
            {
                // first move data on data storage
                Data.Update(item.ParentIdChain, item.DataObject, ParentIds);
            }

            // remove from internal storage, fire move event on the other collection
            ((DomainCollection<T>)item.ParentCollection).DataStorage.Remove(item.Id);
            ((DomainCollection<T>)item.ParentCollection).OnChanged(
                new DomainCollectionChangedEventArgs(DomainCollectionChangedAction.Move, item.ParentCollection, item));

            // overwrite parent
            item.Parent = _parent;

            // add to internal data storage
            DataStorage[item.Id] = item;
            OnChanged(new DomainCollectionChangedEventArgs(DomainCollectionChangedAction.Add, this, item));
        }

        private void AddNew(Dictionary<string, T> dataStorage, T newElement)
        {
            dataStorage[newElement.Id] = newElement;
            OnChanged(new DomainCollectionChangedEventArgs(DomainCollectionChangedAction.Add, this, newElement));
        }

        private bool Remove(T item, bool doUpdate)
        {
            bool itemDeleted = false;
            DataStorage.Remove(item.Id);
            
            if (doUpdate)
            {
                itemDeleted = Data.Delete(ParentIds, item.DataObject);
            }

            // fire remove event so the child elements can be deleted
            OnChanged(new DomainCollectionChangedEventArgs(DomainCollectionChangedAction.Remove, this, item));

            // dispose instance, remove event registrations and clean up
            // memory
            item.Dispose();
            return itemDeleted;
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public event DomainCollectionChangedEventHandler CollectionChanged
        {
            add { _events.Add(EvtKeyChanged, value); }
            remove { _events.Remove(EvtKeyChanged, value); }
        }

        /// <summary>
        /// Fires the Changed event.
        /// </summary>
        /// <param name="args">Arguments to pass to the event receiver.</param>
        protected virtual void OnChanged(DomainCollectionChangedEventArgs args)
        {
            _events.Fire(EvtKeyChanged,this, args);

            ContextInternal.FireCollectionChanged(args);
        }

        private void OnDataAccessObjectChanged(object sender, DataAccessObjectChangedEventArgs e)
        {
            // make sure the change is fired for the current collection instance
            if (!((IDisposableObject)this).IsDisposed && e.ParentIdChain == _parent.IdChain)
            {
                switch (e.Action)
                {
                    case DataAccessObjectChangedAction.Add:
                        // behaviour if an object has been added
                        if (ContainsId(e.ItemIdChain.Top))
                        {
                            Context.TryGetCollectionByChain<T>(e.ItemIdChain.ParentChain).AddInternal(this[e.ItemIdChain.Top], false);
                        }
                        else
                        {
                            AddNew(DataStorage, DomainBase.CreateInstance<T>(_context, e.ChangedItem, _parent));
                        }
                        break;

                    case DataAccessObjectChangedAction.Remove:
                        // behaviour if an object has been removed
                        if (ContainsId(e.ItemIdChain.Top))
                        {
                            Remove(this[e.ItemIdChain.Top], false);
                        }
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }

        #endregion


        //--------------------------------------------------------------------
        // Nested Classes
        //--------------------------------------------------------------------

        /// <summary>
        /// Enum required in order to save the domain collections update
        /// state.
        /// </summary>
        [Flags]
        private enum UpdateState
        {
            None = 0,
            CallbackPending = 1,
            RefreshPending = 2
        }

        /// <summary>
        /// Represents the internal callback handler which is used in order 
        /// to handle and fire the callback events.
        /// </summary>
        private class AsyncCallbackHandler
        {
            #region Declarations
            //--------------------------------------------------------------------
            // Declarations
            //--------------------------------------------------------------------

            private readonly DomainCollection<T> _outer;
            private readonly AsyncCallback _callback;

            private int _loadedElements = 0;
            private bool _abort = false;

            #endregion

            #region Properties
            //--------------------------------------------------------------------
            // Properties
            //--------------------------------------------------------------------

            #endregion

            #region Constructors / Destructor
            //--------------------------------------------------------------------
            // Constructors / Destructor
            //--------------------------------------------------------------------

            internal AsyncCallbackHandler(DomainCollection<T> outer, AsyncCallback callback)
            {
                _outer = outer;
                _callback = callback;
            }

            #endregion

            #region Methods
            //--------------------------------------------------------------------
            // Methods
            //--------------------------------------------------------------------

            internal void InitDeep()
            {
                InitFlat(result =>
                {
                    EnsureContinue(result);
                    CheckLoadedElements(false);
                    LoadDataOnChildren();
                });
            }

            internal void InitFlat()
            {
                InitFlat(_callback);
            }

            private void InitFlat(AsyncCallback callback)
            {
                _outer.Data.GetAll(
                    _outer.ParentIds,
                    result =>
                        {
                            // IMPORTANT: Convert result from DataResult<ICollection<IDataObject>> to DataResult!!

                            if (!result.IsCompleted)
                            {
                                callback(new DataResult(((DataResult<ICollection<IDataObject>>)result).Error));
                            }
                            else
                            {
                                _outer.InitDataStorageElements(((DataResult<ICollection<IDataObject>>) result).Data);
                                callback(DataResult.Empty);
                            }
                        });
            }

            private void LoadDataOnChildren()
            {
                if (_abort)
                    return;

                foreach (KeyValuePair<string, T> valuePair in _outer.DataStorage)
                {
                    valuePair.Value.LoadChildDataAsyncDeep(
                        result =>
                            {
                                EnsureContinue(result);
                                CheckLoadedElements(true);
                            });

                    if (_abort)
                        return;
                }
            }

            private void CheckLoadedElements(bool enlargeCounter)
            {
                if (_abort)
                    return;

                if (enlargeCounter)
                    ++_loadedElements;

                if (_loadedElements == _outer.Count)
                {
                    _callback(DataResult.Empty);
                }
            }

            private void EnsureContinue(IAsyncResult result)
            {
                if (!result.IsCompleted)
                {
                    _abort = true;
                    _callback(result);
                }
            }

            #endregion

            #region Events
            //--------------------------------------------------------------------
            // Events
            //--------------------------------------------------------------------

            #endregion
        }
    }
}